package DEEPERsource.DEEPERsource.source.machinelearning.utils;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import deeper.Direction;
import deeper.Interaction;
import deeper.InteractionType;
import machinelearning.kernel.Kernel;

/**
 * Calculates a kernel for a given dataset.
 * Created to comply with LibSVM classifier.
 * @author tfayruzo
 *
 */
public class PrecomputedKernel {
	private static Log _log = LogFactory.getLog(PrecomputedKernel.class); 

	//two strategies of kernel computation
	//1. Compute test and train matrices (use for train-test experiments)
	//2. Compute the whole matrix once and then just take the values for appropriate pairs (useful for 10 folds)
	// modification of 2th strategy - when kernel was computed in advance, and now only read from file
	
	private Map<KeyPair,Double> full;
	
	
	private double kern[][];
	private double test[][];
	private int trainSize, testSize;
	private Kernel k;
	private Collection<Interaction> fullDS;
	private Collection<Interaction> trainDS;
	private Collection<Interaction> testDS;
	private Map<String,Integer> fullLabels;	
	private int labels[];
	private int labelsTest[];
	public boolean suppressDirection = false;

	/**
	 * 
	 * @param train
	 * @param test
	 * @param kernel
	 */
	public PrecomputedKernel(Collection<Interaction> train, Collection<Interaction> test, Kernel kernel) {
		k = kernel;
		this.trainDS = train;
		this.testDS = test;
		trainSize = trainDS.size();
		testSize = testDS.size();
		kern = new double[trainSize][trainSize];
		this.test = new double[testSize][trainSize];
		labels = new int[trainSize];
		labelsTest = new int[testSize];
	}
	
	public PrecomputedKernel(Collection<Interaction> dataset, Kernel kernel){
		k = kernel;
		fullDS = dataset;
		full = new HashMap<KeyPair,Double>(5000);
		fullLabels = new HashMap<String,Integer>(6000);
	}
	
	public PrecomputedKernel(String kernelFile, String labelFile){
		try {
			full = new HashMap<KeyPair,Double>();
			BufferedReader reader = new BufferedReader(new FileReader(kernelFile));
			String s;
			int i = 0;
			while((s = reader.readLine())!=null){
				_log.debug("Read line "+i);
				String[] split = s.split("\t");
				full.put(new KeyPair(split[0],split[1]), new Double(split[2]));
				i++;
			}
			reader.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			fullLabels = new HashMap<String,Integer>(6000);
			BufferedReader reader = new BufferedReader(new FileReader(labelFile));
			String s;
			while((s = reader.readLine())!=null){
				String[] split = s.split("\t");
				fullLabels.put(split[0], new Integer(split[1]));
			}
			reader.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void calculateFullMatrix() {
		int i = 0, size = fullDS.size();
		for(Interaction i1 : fullDS){
			_log.debug(i+" out of "+size);
			for(Interaction i2 : fullDS){				
				KeyPair key = new KeyPair(i1.id,i2.id);
				if(!full.containsKey(key))
					full.put(key, k.compute(i1, i2));
			}
			i++;
			fullLabels.put(i1.id, getClassLabel(i1));
		}
	} 

	public void calculateTrainingMatrix() {
		int i = 0, j = 0;
		int size = trainDS.size();
		_log.debug("Calculating training matrix");
		for(Interaction i1 : trainDS){
			_log.debug(i+" out of "+size);
			for(Interaction i2 : trainDS){
				kern[i][j++] = k.compute(i1, i2);
			}
			labels[i++] = getClassLabel(i1);			
			j = 0;
		}
	}
	
	public void calculateTestMatrix() {
		int i = 0;
		int size = testDS.size();
		_log.debug("Calculating test matrix");
		for(Interaction i1 : testDS){
			int j = 0;
			_log.debug(i+" out of "+size);
			for(Interaction i2 : trainDS){
				test[i][j++] = k.compute(i1, i2);
			}
			labelsTest[i++] = getClassLabel(i1);
		}
	}
	
	public void printTrain(PrintWriter stream, Collection<Interaction> train){
		int i = 1;
		for(Interaction i1 : train){
			stream.print(fullLabels.get(i1.id));
			stream.print("\t");
			stream.print("0:"+Integer.toString(i++));
			stream.print("\t");
			int j = 1;
			for(Interaction i2 : train){
				stream.print(j++);
				stream.print(":");
				stream.print(full.get(new KeyPair(i1.id,i2.id)));
				stream.print("\t");
			}
			stream.println();
		}		
	}
	
	public void printTrain4Precomp(PrintWriter stream, Collection<String> train){
		int i = 1;
		for(String i1 : train){
			stream.print(fullLabels.get(i1));
			stream.print("\t");
			stream.print("0:"+Integer.toString(i++));
			stream.print("\t");
			int j = 1;
			for(String i2 : train){
				stream.print(j++);
				stream.print(":");
				stream.print(full.get(new KeyPair(i1,i2)));
				stream.print("\t");
			}
			stream.println();
		}		
	}
	
	public void printTest4Precomp(PrintWriter stream, Collection<String> train, Collection<String> test){
		int i = 1;
		for(String i1 : test){
			stream.print(fullLabels.get(i1));
			stream.print("\t");
			stream.print("0:"+Integer.toString(i++));
			stream.print("\t");
			int j = 1;
			for(String i2 : train){
				stream.print(j++);
				stream.print(":");
				stream.print(full.get(new KeyPair(i1, i2)));
				stream.print("\t");
			}
			stream.println();
		}		
	}
	
	public void printTest(PrintWriter stream, Collection<Interaction> train, Collection<Interaction> test){
		int i = 1;
		for(Interaction i1 : test){
			stream.print(fullLabels.get(i1.id));
			stream.print("\t");
			stream.print("0:"+Integer.toString(i++));
			stream.print("\t");
			int j = 1;
			for(Interaction i2 : train){
				stream.print(j++);
				stream.print(":");
				stream.print(full.get(new KeyPair(i1.id,i2.id)));
				stream.print("\t");
			}
			stream.println();
		}		
	}
	
	public void printTrain(PrintWriter stream){
		for(int i = 0; i<trainSize ; i++){
			stream.print(labels[i]);
			stream.print("\t");
			stream.print("0:"+Integer.toString(i+1));
			stream.print("\t");
			for(int j = 0; j<trainSize ; j++){
				stream.print(j+1);
				stream.print(":");
				stream.print(kern[i][j]);
				stream.print("\t");
			}
			stream.println();
		}		
	}
	
	public void printTest(PrintWriter stream){
		for(int i = 0; i<testSize ; i++){
			stream.print(labelsTest[i]);
			stream.print("\t");
			stream.print("0:"+Integer.toString(i+1));
			stream.print("\t");
			for(int j = 0; j<trainSize ; j++){
				stream.print(j+1);
				stream.print(":");
				stream.print(test[i][j]);
				stream.print("\t");
			}
			stream.println();
		}		
	}
	
	public void directPrintFullKernel(PrintWriter stream){
		int size = fullDS.size();
		for(int i = 0; i < size-1; i++){
			_log.debug(i+" out of "+size);
			Interaction i1 = ((List<Interaction>)fullDS).get(i);
			for(int j = i+1; j < size; j++){
				Interaction i2 = ((List<Interaction>)fullDS).get(j);
				double kern = k.compute(i1, i2);
				stream.print(i1.id+"\t"+i2.id);
				stream.print("\t");
				stream.print(kern);
				stream.print("\n");				
			}
			fullLabels.put(i1.id, getClassLabel(i1));
		}		
	}
	
	public void directPrintFullLabels(PrintWriter stream){
		for(Interaction i : fullDS){
			stream.print(i.id);
			stream.print("\t");
			stream.print(getClassLabel(i));
			stream.print("\n");
		}	
	}
	
	private int getClassLabel(Interaction i){
		int cl = InteractionType.FALSE.ordinal();
		if(suppressDirection){
			if(i.status.ordinal()!=cl)
				cl = InteractionType.REAL.ordinal();
		}else{
			cl = i.status.ordinal();
		}
		return cl;
	}
	
	public double compute(String id1, String id2){
		return full.get(new KeyPair(id1, id2));
	}
	
	public void eraseKern(){
		kern = null;
		labels = null;
	}
	
	public void eraseTest(){
		test = null;
		labelsTest = null;
	}
	
	class KeyPair{
		String id1, id2;
		
		KeyPair(String s1, String s2){
			id1 = s1; id2 = s2;
		}

		@Override
		public int hashCode() {
			final int PRIME = 31;
			int result = 1;
			result = PRIME * result + ((id1 == null) ? 0 : id1.hashCode());
			result = PRIME * result + ((id2 == null) ? 0 : id2.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj){
			Set<String> ids = new HashSet<String>();
			ids.add(((KeyPair)obj).id1);
			ids.add(((KeyPair)obj).id2);
			if(ids.contains(id1) && ids.contains(id2))
				return true;
			return false;
		}
		
		public String toString(){
			return id1+"\t"+id2;
		}
	}
}